﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace AssemblerCompiler
{
    public static class Compiler
    {
        public static TranslationResult Compile(string source)
        {
            string[] rows = source.Split(new[] {'\n'});
            for (int i = 0; i < rows.Length; ++i)
            {
                if (rows[i].Length != 0 && rows[i][rows[i].Length - 1] == '\r')
                {
                    rows[i] = rows[i].Substring(0, rows[i].Length - 1);
                }
            }
            var res = new TranslationResult();

            var doubleWordValues = new Dictionary<UInt16, UInt16>();
            var byteValues = new Dictionary<UInt16, Byte>();

            var cmds = new List<Function>();
            var rowInds = new List<int>();

            Function curCmd;

            bool isProcedureStarted = false;
            string procedureStartedName = "";
            string procedureName;
            string variableName;
            UInt16 currentAddress = 0;

            var orgRegex =
                new Regex(@"^\s*ORG\s+((?<hex>[0-9][0-9A-F]*)H|(?<dec>[0-9]+)|(?<dec1>[0-9]+)D|(?<bin>[0-1]+)B)\s*$",
                          RegexOptions.IgnoreCase);
            var labelRegex = new Regex(@"^\s*(?<name>[_A-Z][_A-Z0-9]*)\s*:", RegexOptions.IgnoreCase);
            var procRegex = new Regex(@"^\s*(?<name>[_A-Z][_A-Z0-9]*)\s+PROC\s*$", RegexOptions.IgnoreCase);
            var endpRegex = new Regex(@"^\s*(?<name>[_A-Z][_A-Z0-9]*)\s+ENDP\s*$", RegexOptions.IgnoreCase);
            var dwNamedRegex =
                new Regex(
                    @"^\s*(?<name>([_A-Z][_A-Z0-9]*))\s+DW\s+(((?<hex>[0-9][0-9A-F]*)H|(?<dec>[0-9]+)|(?<dec1>[0-9]+)D|(?<bin>[0-1]+)B)|\?|(?<num>((?<hex_>[0-9][0-9A-F]*)H|(?<dec_>[0-9]+)|(?<dec1_>[0-9]+)D|(?<bin_>[0-1]+)B))\s+DUP\s*\(\s*(?<val>((?<hex__>[0-9][0-9A-F]*)H|(?<dec__>[0-9]+)|(?<dec1__>[0-9]+)D|(?<bin__>[0-1]+)B))\s*\))\s*$",
                    RegexOptions.IgnoreCase);
            var dwUnnamedRegex =
                new Regex(
                    @"^\s*DW\s+(((?<hex>[0-9][0-9A-F]*)H|(?<dec>[0-9]+)|(?<dec1>[0-9]+)D|(?<bin>[0-1]+)B)|\?|(?<num>((?<hex_>[0-9][0-9A-F]*)H|(?<dec_>[0-9]+)|(?<dec1_>[0-9]+)D|(?<bin_>[0-1]+)B))\s+DUP\s*\(\s*(?<val>((?<hex__>[0-9][0-9A-F]*)H|(?<dec__>[0-9]+)|(?<dec1__>[0-9]+)D|(?<bin__>[0-1]+)B))\s*\))\s*$",
                    RegexOptions.IgnoreCase);
            var dbNamedRegex =
                new Regex(
                    @"^\s*(?<name>([_A-Z][_A-Z0-9]*))\s+DB\s+(((?<hex>[0-9][0-9A-F]*)H|(?<dec>[0-9]+)|(?<dec1>[0-9]+)D|(?<bin>[0-1]+)B)|\?|'(?<str>[A-Z0-9_ \$\.,!\?\+=\*/-]+)'|(?<num>((?<hex_>[0-9][0-9A-F]*)H|(?<dec_>[0-9]+)|(?<dec1_>[0-9]+)D|(?<bin_>[0-1]+)B))\s+DUP\s*\(\s*(?<val>((?<hex__>[0-9][0-9A-F]*)H|(?<dec__>[0-9]+)|(?<dec1__>[0-9]+)D|(?<bin__>[0-1]+)B))\s*\))\s*$",
                    RegexOptions.IgnoreCase);
            var dbUnnamedRegex =
                new Regex(
                    @"^\s*DB\s+(((?<hex>[0-9][0-9A-F]*)H|(?<dec>[0-9]+)|(?<dec1>[0-9]+)D|(?<bin>[0-1]+)B)|\?|'(?<str>[A-Z0-9_ \$\.,!\?\+\=\*/-]+)'|(?<num>((?<hex_>[0-9][0-9A-F]*)H|(?<dec_>[0-9]+)|(?<dec1_>[0-9]+)D|(?<bin_>[0-1]+)B))\s+DUP\s*\(\s*(?<val>((?<hex__>[0-9][0-9A-F]*)H|(?<dec__>[0-9]+)|(?<dec1__>[0-9]+)D|(?<bin__>[0-1]+)B))\s*\))\s*$",
                    RegexOptions.IgnoreCase);
            var emptyRegex = new Regex(@"^\s*$");

            Match match;
            string labelName;

            #region first view

            for (int i = 0; i < rows.Length; ++i)
            {
                try
                {
                    rows[i] = RemoveComments(rows[i]);

                    //check label
                    match = labelRegex.Match(rows[i]);
                    if (match.Success)
                    {
                        labelName = match.Groups["name"].Value.ToUpper();
                        if (res.SymTabContains(labelName, SymTabElement.SymType.Label))
                        {
                            throw new Exception("There are two or more labels with the same name " + labelName + " !");
                        }
                        res.AddSymTabElement(new SymTabElement(labelName, currentAddress, SymTabElement.SymType.Label));
                        rows[i] = rows[i].Substring(match.Length);
                    }

                    //check org directive
                    match = orgRegex.Match(rows[i]);
                    if (match.Success)
                    {
                        if (match.Groups["hex"].Value != String.Empty)
                        {
                            currentAddress = ConverterClass.HexToUInt16(match.Groups["hex"].Value);
                        }
                        else if (match.Groups["dec"].Value != String.Empty || match.Groups["dec1"].Value != String.Empty)
                        {
                            currentAddress = ConverterClass.BinToUInt16(match.Groups["dec1"].Value);
                        }
                        else if (match.Groups["bin"].Value != String.Empty)
                        {
                            currentAddress = ConverterClass.DecToUInt16(match.Groups["bin"].Value);
                        }
                        rows[i] = "";
                        continue;
                    }

                    //check proc directive
                    match = procRegex.Match(rows[i]);
                    if (match.Success)
                    {
                        procedureName = match.Groups["name"].Value.ToUpper();
                        if (isProcedureStarted)
                        {
                            throw new Exception("Declaring procedure " + procedureName + " inside procedure " +
                                                procedureStartedName + "!");
                        }
                        if (res.SymTabContains(procedureName, SymTabElement.SymType.Procedure))
                        {
                            throw new Exception("There are two or more prosecures with the same name " + procedureName +
                                                "!");
                        }
                        res.AddSymTabElement(new SymTabElement(procedureName, currentAddress,
                                                               SymTabElement.SymType.Procedure));
                        isProcedureStarted = true;
                        procedureStartedName = procedureName;
                        rows[i] = "";
                        continue;
                    }

                    //check endp directive
                    match = endpRegex.Match(rows[i]);
                    if (match.Success)
                    {
                        procedureName = match.Groups["name"].Value.ToUpper();
                        if (!isProcedureStarted)
                        {
                            throw new Exception("Error in statement - using directive endp " + procedureName +
                                                " without directive proc " + procedureName + "!");
                        }
                        if (procedureStartedName != procedureName)
                        {
                            throw new Exception("Error in statement - declaring begin of procedure " +
                                                procedureStartedName + " and end of procedure " + procedureName + "!");
                        }
                        isProcedureStarted = false;
                        rows[i] = "";
                    }

                    //check double word definition
                    match = dwNamedRegex.Match(rows[i]);
                    if (match.Success)
                    {
                        variableName = match.Groups["name"].Value.ToUpper();
                        if (res.SymTabContains(variableName, SymTabElement.SymType.Pointer))
                        {
                            throw new Exception("Redefinition, there was already declared pointer with name " +
                                                variableName + "!");
                        }

                        res.AddSymTabElement(new SymTabElement(variableName, currentAddress,
                                                               SymTabElement.SymType.Pointer));

                        if (match.Groups["hex"].Value != String.Empty)
                        {
                            doubleWordValues.Add(currentAddress, ConverterClass.HexToUInt16(match.Groups["hex"].Value));
                        }
                        else if (match.Groups["dec"].Value != String.Empty)
                        {
                            doubleWordValues.Add(currentAddress, ConverterClass.DecToUInt16(match.Groups["dec"].Value));
                        }
                        else if (match.Groups["dec1"].Value != String.Empty)
                        {
                            doubleWordValues.Add(currentAddress, ConverterClass.DecToUInt16(match.Groups["dec1"].Value));
                        }
                        else if (match.Groups["bin"].Value != String.Empty)
                        {
                            doubleWordValues.Add(currentAddress, ConverterClass.BinToUInt16(match.Groups["bin"].Value));
                        }
                        else if (match.Groups["num"].Value != String.Empty)
                        {
                            Byte num = 0;
                            if (match.Groups["hex_"].Value != String.Empty)
                            {
                                num = ConverterClass.HexToByte(match.Groups["hex_"].Value);
                            }
                            else if (match.Groups["dec_"].Value != String.Empty)
                            {
                                num = ConverterClass.DecToByte(match.Groups["dec_"].Value);
                            }
                            else if (match.Groups["dec1_"].Value != String.Empty)
                            {
                                num = ConverterClass.DecToByte(match.Groups["dec1_"].Value);
                            }
                            else if (match.Groups["bin_"].Value != String.Empty)
                            {
                                num = ConverterClass.BinToByte(match.Groups["bin_"].Value);
                            }
                            else
                            {
                                throw new Exception("Internal compiler error!");
                            }
                            UInt16 val = 0;
                            if (match.Groups["hex__"].Value != String.Empty)
                            {
                                val = ConverterClass.HexToUInt16(match.Groups["hex__"].Value);
                            }
                            else if (match.Groups["dec__"].Value != String.Empty)
                            {
                                val = ConverterClass.DecToUInt16(match.Groups["dec__"].Value);
                            }
                            else if (match.Groups["dec1__"].Value != String.Empty)
                            {
                                val = ConverterClass.DecToUInt16(match.Groups["dec1__"].Value);
                            }
                            else if (match.Groups["bin__"].Value != String.Empty)
                            {
                                val = ConverterClass.BinToUInt16(match.Groups["bin__"].Value);
                            }
                            else
                            {
                                throw new Exception("Internal compiler error!");
                            }
                            for (int j = 0; j < num; ++j)
                            {
                                doubleWordValues.Add(currentAddress, val);
                                currentAddress += 2;
                            }
                        }
                        else
                        {
                            byteValues.Add(currentAddress, 0);
                        }

                        if (match.Groups["num"].Value == String.Empty)
                        {
                            currentAddress += 2;
                        }

                        rows[i] = "";
                    }
                    match = dwUnnamedRegex.Match(rows[i]);
                    if (match.Success)
                    {
                        if (match.Groups["hex"].Value != String.Empty)
                        {
                            doubleWordValues.Add(currentAddress, ConverterClass.HexToUInt16(match.Groups["hex"].Value));
                        }
                        else if (match.Groups["dec"].Value != String.Empty)
                        {
                            doubleWordValues.Add(currentAddress, ConverterClass.DecToUInt16(match.Groups["dec"].Value));
                        }
                        else if (match.Groups["dec1"].Value != String.Empty)
                        {
                            doubleWordValues.Add(currentAddress, ConverterClass.DecToUInt16(match.Groups["dec1"].Value));
                        }
                        else if (match.Groups["bin"].Value != String.Empty)
                        {
                            doubleWordValues.Add(currentAddress, ConverterClass.BinToUInt16(match.Groups["bin"].Value));
                        }
                        else if (match.Groups["num"].Value != String.Empty)
                        {
                            Byte num = 0;
                            if (match.Groups["hex_"].Value != String.Empty)
                            {
                                num = ConverterClass.HexToByte(match.Groups["hex_"].Value);
                            }
                            else if (match.Groups["dec_"].Value != String.Empty)
                            {
                                num = ConverterClass.DecToByte(match.Groups["dec_"].Value);
                            }
                            else if (match.Groups["dec1_"].Value != String.Empty)
                            {
                                num = ConverterClass.DecToByte(match.Groups["dec1_"].Value);
                            }
                            else if (match.Groups["bin_"].Value != String.Empty)
                            {
                                num = ConverterClass.BinToByte(match.Groups["bin_"].Value);
                            }
                            else
                            {
                                throw new Exception("Internal compiler error!");
                            }
                            UInt16 val = 0;
                            if (match.Groups["hex__"].Value != String.Empty)
                            {
                                val = ConverterClass.HexToUInt16(match.Groups["hex__"].Value);
                            }
                            else if (match.Groups["dec__"].Value != String.Empty)
                            {
                                val = ConverterClass.DecToUInt16(match.Groups["dec__"].Value);
                            }
                            else if (match.Groups["dec1__"].Value != String.Empty)
                            {
                                val = ConverterClass.DecToUInt16(match.Groups["dec1__"].Value);
                            }
                            else if (match.Groups["bin__"].Value != String.Empty)
                            {
                                val = ConverterClass.BinToUInt16(match.Groups["bin__"].Value);
                            }
                            else
                            {
                                throw new Exception("Internal compiler error!");
                            }
                            for (int j = 0; j < num; ++j)
                            {
                                doubleWordValues.Add(currentAddress, val);
                                currentAddress += 2;
                            }
                        }
                        else
                        {
                            byteValues.Add(currentAddress, 0);
                        }

                        if (match.Groups["num"].Value == String.Empty)
                        {
                            currentAddress += 2;
                        }

                        rows[i] = "";
                    }

                    //check byte definition
                    match = dbNamedRegex.Match(rows[i]);
                    if (match.Success)
                    {
                        variableName = match.Groups["name"].Value.ToUpper();
                        if (res.SymTabContains(variableName, SymTabElement.SymType.Pointer))
                        {
                            throw new Exception("Redefinition, there was already declared pointer with name " +
                                                variableName + "!");
                        }

                        res.AddSymTabElement(new SymTabElement(variableName, currentAddress,
                                                               SymTabElement.SymType.Pointer));

                        if (match.Groups["hex"].Value != String.Empty)
                        {
                            byteValues.Add(currentAddress, ConverterClass.HexToByte(match.Groups["hex"].Value));
                        }
                        else if (match.Groups["dec"].Value != String.Empty)
                        {
                            byteValues.Add(currentAddress, ConverterClass.DecToByte(match.Groups["dec"].Value));
                        }
                        else if (match.Groups["dec1"].Value != String.Empty)
                        {
                            byteValues.Add(currentAddress, ConverterClass.DecToByte(match.Groups["dec1"].Value));
                        }
                        else if (match.Groups["bin"].Value != String.Empty)
                        {
                            byteValues.Add(currentAddress, ConverterClass.BinToByte(match.Groups["bin"].Value));
                        }
                        else if (match.Groups["str"].Value != String.Empty)
                        {
                            string str = match.Groups["str"].Value;
                            for (int j = 0; j < str.Length; ++j)
                            {
                                byteValues.Add(currentAddress++, (Byte) str[j]);
                            }
                        }
                        else if (match.Groups["num"].Value != String.Empty)
                        {
                            Byte num = 0;
                            if (match.Groups["hex_"].Value != String.Empty)
                            {
                                num = ConverterClass.HexToByte(match.Groups["hex_"].Value);
                            }
                            else if (match.Groups["dec_"].Value != String.Empty)
                            {
                                num = ConverterClass.DecToByte(match.Groups["dec_"].Value);
                            }
                            else if (match.Groups["dec1_"].Value != String.Empty)
                            {
                                num = ConverterClass.DecToByte(match.Groups["dec1_"].Value);
                            }
                            else if (match.Groups["bin_"].Value != String.Empty)
                            {
                                num = ConverterClass.BinToByte(match.Groups["bin_"].Value);
                            }
                            else
                            {
                                throw new Exception("Internal compiler error!");
                            }
                            Byte val = 0;
                            if (match.Groups["hex__"].Value != String.Empty)
                            {
                                val = ConverterClass.HexToByte(match.Groups["hex__"].Value);
                            }
                            else if (match.Groups["dec__"].Value != String.Empty)
                            {
                                val = ConverterClass.DecToByte(match.Groups["dec__"].Value);
                            }
                            else if (match.Groups["dec1__"].Value != String.Empty)
                            {
                                val = ConverterClass.DecToByte(match.Groups["dec1__"].Value);
                            }
                            else if (match.Groups["bin__"].Value != String.Empty)
                            {
                                val = ConverterClass.BinToByte(match.Groups["bin__"].Value);
                            }
                            else
                            {
                                throw new Exception("Internal compiler error!");
                            }
                            for (int j = 0; j < num; ++j)
                            {
                                byteValues.Add(currentAddress++, val);
                            }
                        }
                        else
                        {
                            byteValues.Add(currentAddress, 0);
                        }

                        if (match.Groups["str"].Value == String.Empty && match.Groups["num"].Value == String.Empty)
                        {
                            currentAddress += 1;
                        }

                        rows[i] = "";
                    }
                    match = dbUnnamedRegex.Match(rows[i]);
                    if (match.Success)
                    {
                        if (match.Groups["hex"].Value != String.Empty)
                        {
                            byteValues.Add(currentAddress, ConverterClass.HexToByte(match.Groups["hex"].Value));
                        }
                        else if (match.Groups["dec"].Value != String.Empty)
                        {
                            byteValues.Add(currentAddress, ConverterClass.DecToByte(match.Groups["dec"].Value));
                        }
                        else if (match.Groups["dec1"].Value != String.Empty)
                        {
                            byteValues.Add(currentAddress, ConverterClass.DecToByte(match.Groups["dec1"].Value));
                        }
                        else if (match.Groups["bin"].Value != String.Empty)
                        {
                            byteValues.Add(currentAddress, ConverterClass.BinToByte(match.Groups["bin"].Value));
                        }
                        else if (match.Groups["str"].Value != String.Empty)
                        {
                            string str = match.Groups["str"].Value;
                            for (int j = 0; j < str.Length; ++j)
                            {
                                byteValues.Add(currentAddress++, (Byte) str[j]);
                            }
                        }
                        else if (match.Groups["num"].Value != String.Empty)
                        {
                            Byte num = 0;
                            if (match.Groups["hex_"].Value != String.Empty)
                            {
                                num = ConverterClass.HexToByte(match.Groups["hex_"].Value);
                            }
                            else if (match.Groups["dec_"].Value != String.Empty)
                            {
                                num = ConverterClass.DecToByte(match.Groups["dec_"].Value);
                            }
                            else if (match.Groups["dec1_"].Value != String.Empty)
                            {
                                num = ConverterClass.DecToByte(match.Groups["dec1_"].Value);
                            }
                            else if (match.Groups["bin_"].Value != String.Empty)
                            {
                                num = ConverterClass.BinToByte(match.Groups["bin_"].Value);
                            }
                            else
                            {
                                throw new Exception("Internal compiler error!");
                            }
                            Byte val = 0;
                            if (match.Groups["hex__"].Value != String.Empty)
                            {
                                val = ConverterClass.HexToByte(match.Groups["hex__"].Value);
                            }
                            else if (match.Groups["dec__"].Value != String.Empty)
                            {
                                val = ConverterClass.DecToByte(match.Groups["dec__"].Value);
                            }
                            else if (match.Groups["dec1__"].Value != String.Empty)
                            {
                                val = ConverterClass.DecToByte(match.Groups["dec1__"].Value);
                            }
                            else if (match.Groups["bin__"].Value != String.Empty)
                            {
                                val = ConverterClass.BinToByte(match.Groups["bin__"].Value);
                            }
                            else
                            {
                                throw new Exception("Internal compiler error!");
                            }
                            for (int j = 0; j < num; ++j)
                            {
                                byteValues.Add(currentAddress++, val);
                            }
                        }
                        else
                        {
                            byteValues.Add(currentAddress, 0);
                        }

                        if (match.Groups["str"].Value == String.Empty && match.Groups["num"].Value == String.Empty)
                        {
                            currentAddress += 1;
                        }

                        rows[i] = "";
                    }

                    //check empty string
                    match = emptyRegex.Match(rows[i]);
                    if (match.Success)
                    {
                        continue;
                    }

                    curCmd = new Function(rows[i], currentAddress);

                    cmds.Add(curCmd);
                    rowInds.Add(i);

                    currentAddress += curCmd.Size;
                }
                catch (Exception e)
                {
                    res.ErrorReports.Add(i + 1, e.Message);
                }
            }

            #endregion

            #region second view

            res.SetLength(currentAddress);

            foreach (var cur in doubleWordValues)
            {
                res.Code[cur.Key] = (Byte) (cur.Value & 255);
                res.Code[cur.Key + 1] = (Byte) (cur.Value >> 8);
            }

            foreach (var cur in byteValues)
            {
                res.Code[cur.Key] = cur.Value;
            }

            for (int i = 0; i < cmds.Count; ++i)
            {
                if (res.ErrorReports.ContainsKey(rowInds[i] + 1))
                {
                    continue;
                }
                try
                {
                    cmds[i].SetMemoryAddress(res);
                    for (int j = 0; j < cmds[i].Size; ++j)
                    {
                        res.Code[cmds[i].Address + j] = cmds[i].GetByte(j);
                    }
                }
                catch (Exception e)
                {
                    res.ErrorReports.Add(rowInds[i] + 1, e.Message);
                }
            }

            #endregion

            return res;
        }

        private static string RemoveComments(string row)
        {
            bool isEnclosed = false;

            for (int i = 0; i < row.Length; ++i)
            {
                if (row[i] == '\'')
                {
                    isEnclosed = !isEnclosed;
                }
                if (row[i] == ';' && !isEnclosed)
                {
                    return row.Substring(0, i);
                }
            }

            return row;
        }
    }
}